import pathlib, os, pickle

from uuid import UUID

from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QStyle, QFileDialog, QDialog, QLineEdit, QListWidget, QListWidgetItem, QCheckBox
from PyQt5.QtCore import Qt, QThreadPool, pyqtSignal

from protolib import correct_path

from data import database

from sync import sync, get_sync_db_template
from repair import repair_sync_db

from player.worker import Worker

class SyncDialog(QDialog):
    current_path_updated = pyqtSignal(bool)
    sync_db_reloaded = pyqtSignal(bool)
    sync_db_changed = pyqtSignal()
    sync_db_saved = pyqtSignal()

    def __init__(self, parent):
        super().__init__(parent)

        self.threadpool = QThreadPool()
        print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())

        self.setWindowTitle('Sync Library')

        layout = QVBoxLayout()

        #region path
        self.path_label = QLabel('Current path:')
        layout.addWidget(self.path_label)

        path_edit_layout = QHBoxLayout()

        self.path_edit = QLineEdit()
        self.path_edit.setPlaceholderText('Enter path here')
        self.path_edit.editingFinished.connect(self.path_editing_finished)
        path_edit_layout.addWidget(self.path_edit)

        self.path_button = QPushButton()
        self.path_button.setIcon(self.style().standardIcon(QStyle.SP_DirIcon))
        self.path_button.clicked.connect(self.ask_new_path)
        path_edit_layout.addWidget(self.path_button)

        layout.addLayout(path_edit_layout)

        self.confirm_path_button = QPushButton('Confirm')
        self.confirm_path_button.clicked.connect(self.path_editing_finished)
        layout.addWidget(self.confirm_path_button)
        #endregion

        #region blacklist
        self.blacklist_label = QLabel('Tracks that won\'t be synced:')
        layout.addWidget(self.blacklist_label)

        self.blacklist_widget = QListWidget()
        self.blacklist_widget.itemChanged.connect(self.blacklist_changed)
        layout.addWidget(self.blacklist_widget)
        #endregion

        #region config
        self.config_label = QLabel('Sync configuration:')
        layout.addWidget(self.config_label)

        self.write_meta_checkbox = QCheckBox('Overwrite metadata')
        self.write_meta_checkbox.stateChanged.connect(self.write_meta_checkbox_state_changed)
        layout.addWidget(self.write_meta_checkbox)

        #region path template
        self.path_template_checkbox = QCheckBox('Use custom path template')
        self.path_template_checkbox.stateChanged.connect(self.path_template_checkbox_state_changed)
        layout.addWidget(self.path_template_checkbox)

        self.path_template_edit = QLineEdit()
        self.path_template_edit.setPlaceholderText('Enter path template here')
        self.path_template_edit.editingFinished.connect(self.path_template_edit_editing_finished)
        layout.addWidget(self.path_template_edit)
        #endregion

        #region meta artist seperator
        meta_artist_seperator_layout = QHBoxLayout()
        self.meta_artist_seperator_label = QLabel('Metadata artist seperator:')
        meta_artist_seperator_layout.addWidget(self.meta_artist_seperator_label)

        self.meta_artist_seperator_edit = QLineEdit()
        self.meta_artist_seperator_edit.setPlaceholderText('Enter metadata artist seperator here')
        self.meta_artist_seperator_edit.editingFinished.connect(self.meta_artist_seperator_editing_finished)
        meta_artist_seperator_layout.addWidget(self.meta_artist_seperator_edit)
        layout.addLayout(meta_artist_seperator_layout)
        #endregion

        #region file artist seperator
        file_artist_seperator_layout = QHBoxLayout()
        self.file_artist_seperator_label = QLabel('File name artist seperator:')
        file_artist_seperator_layout.addWidget(self.file_artist_seperator_label)

        self.file_artist_seperator_edit = QLineEdit()
        self.file_artist_seperator_edit.setPlaceholderText('Enter file name artist seperator here')
        self.file_artist_seperator_edit.editingFinished.connect(self.file_artist_seperator_editing_finished)
        file_artist_seperator_layout.addWidget(self.file_artist_seperator_edit)
        layout.addLayout(file_artist_seperator_layout)
        #endregion
        #endregion

        #region status
        self.status_label = QLabel('Status: Ready')
        layout.addWidget(self.status_label)

        self.uuid_label = QLabel('UUID:')
        self.uuid_label.setDisabled(True)
        layout.addWidget(self.uuid_label)

        self.name_label = QLabel('Name:')
        self.name_label.setDisabled(True)
        layout.addWidget(self.name_label)

        self.artist_label = QLabel('Artists: []')
        self.artist_label.setDisabled(True)
        layout.addWidget(self.artist_label)
        #endregion

        #region sync
        self.sync_start_button = QPushButton('Start')
        self.sync_start_button.clicked.connect(self.sync_lib)
        layout.addWidget(self.sync_start_button)

        self.make_syncable_button = QPushButton('Make directory syncable')
        self.make_syncable_button.clicked.connect(self.create_sync_db)
        layout.addWidget(self.make_syncable_button)
        #endregion

        self.setLayout(layout)

        #region Connect signals
        self.current_path_updated.connect(self.handle_syncable_directory)
        self.current_path_updated.connect(self.load_sync_db)

        self.sync_db_reloaded.connect(self.load_sync_config)
        self.sync_db_reloaded.connect(self.refresh_blacklist)

        self.finished.connect(self.dump_sync_db)
        #endregion

        #region
        self.path_editing_finished()
        #endregion

    def handle_syncable_directory(self, valid: bool):
        self.blacklist_widget.setEnabled(valid)
        self.write_meta_checkbox.setEnabled(valid)
        self.path_template_checkbox.setEnabled(valid)
        self.path_template_edit.setEnabled(valid)
        self.meta_artist_seperator_edit.setEnabled(valid)
        self.file_artist_seperator_edit.setEnabled(valid)
        self.meta_artist_seperator_label.setEnabled(valid)
        self.file_artist_seperator_label.setEnabled(valid)
        self.sync_start_button.setEnabled(valid)
        self.make_syncable_button.setEnabled(not valid)

    def path_editing_finished(self):
        ret_val = True
        text = self.path_edit.text()
        if text:
            path = pathlib.Path(text)
        else:
            path = pathlib.Path('~')
        path = path.expanduser()
        path = str(path)
        new_path = path.replace('\\', '/')
        for char in '<>:"|?*':
            new_path = new_path.replace(char, '')
        new_path_parts: list[str] = new_path.split('/')
        new_path = ''
        for part in new_path_parts:
            part = part.strip()
            part = part.strip('.')
            new_path += (part + '/')
        new_path = new_path.strip('/')
        path = pathlib.Path(path)
        if not path.exists():
            ret_val = False
        elif not path.is_dir():
            ret_val = False
        elif not os.path.exists(os.path.join(path, 'sync.pkl')):
            ret_val = False
        self.path_edit.setText(str(path))
        self.current_path_updated.emit(ret_val)

    def ask_new_path(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        file_path = QFileDialog.getExistingDirectory(self, "Open directory", "", options=options)
        if file_path:
            self.path_edit.setText(file_path)
            self.path_editing_finished()

    def clear_sync_config(self):
        self.blacklist_widget.clear()
        self.write_meta_checkbox.setChecked(False)
        self.path_template_checkbox.setChecked(False)
        self.path_template_edit.clear()
        self.meta_artist_seperator_edit.clear()
        self.file_artist_seperator_edit.clear()

    def load_sync_db(self, enable: bool):
        print('Loading sync database')
        text = self.path_edit.text()
        if not os.path.exists(os.path.join(text, 'sync.pkl')):
            self.sync_db = None
            self.sync_db_reloaded.emit(False)
            return
        text = self.path_edit.text()
        path = os.path.join(text, 'sync.pkl')
        with open(path, 'rb') as f:
            db = pickle.load(f)
        self.sync_db = db
        self.sync_db_reloaded.emit(True)

    def load_sync_config(self, enable: bool):
        if not enable:
            return

        self.path_template_edit.setText(self.sync_db['templates']['path'])
        self.write_meta_checkbox.setChecked(self.sync_db['music']['overwrite']['metadata'])
        self.path_template_checkbox.setChecked(self.sync_db['music']['overwrite']['path'])

        self.meta_artist_seperator_edit.setText(self.sync_db['templates']['artist']['seperator']['filename'])
        self.file_artist_seperator_edit.setText(self.sync_db['templates']['artist']['seperator']['metadata'])

    def path_template_checkbox_state_changed(self):
        checked = self.path_template_checkbox.checkState()
        self.path_template_edit.setEnabled(checked)
        if self.sync_db:
            self.sync_db['music']['overwrite']['path'] = int(checked) > 0
        self.sync_db_changed.emit()

    def write_meta_checkbox_state_changed(self):
        checked = self.write_meta_checkbox.checkState()
        if self.sync_db:
            self.sync_db['music']['overwrite']['metadata'] = int(checked) > 0
        self.sync_db_changed.emit()

    def path_template_edit_editing_finished(self):
        if self.sync_db:
            text = self.path_template_edit.text()
            self.sync_db['templates']['path'] = text
            self.sync_db_changed.emit()

    def refresh_blacklist(self, enable: bool):
        if not enable:
            return

        self.blacklist_widget.clear()
        self.blacklist_widget_items: dict[UUID, QListWidgetItem] = {}

        strings = {}
        for uuid in database['music']:
            strings[uuid] = '%s by %s from %s by %s (%s)'
            artist = str(list(database['artists'][artist_uuid]['name'] for artist_uuid in database['music'][uuid]['artists'])) if database['music'][uuid]['artists'] else 'Unknown'
            title = database['music'][uuid]['title'] if database['music'][uuid]['title'] else 'Unknown'
            album = database['albums'][database['music'][uuid]['album']]['name'] if database['music'][uuid]['album'] else 'Unknown'
            album_artist = ((database['artists'][database['albums'][database['music'][uuid]['album']]['artist']]['name']) if database['albums'][database['music'][uuid]['album']]['artist'] else 'Unknown') if database['music'][uuid]['album'] else 'Unknown'
            strings[uuid] = strings[uuid] % (title, artist, album, album_artist, str(uuid))
        uuids = list(strings.keys())
        uuids.sort(key=lambda uuid: strings[uuid])
        strings = {uuid: strings[uuid] for uuid in uuids}

        for uuid in strings:
            string = strings[uuid]
            item = QListWidgetItem(string)
            item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
            if uuid in self.sync_db['music']['blacklist']:
                item.setCheckState(Qt.Checked)
            else:
                item.setCheckState(Qt.Unchecked)
            self.blacklist_widget_items[uuid] = item
            self.blacklist_widget.addItem(item)

    def blacklist_changed(self):
        self.sync_db['music']['blacklist'].clear()
        for uuid in self.blacklist_widget_items:
            item = self.blacklist_widget_items[uuid]
            if item.checkState():
                self.sync_db['music']['blacklist'].append(uuid)
        self.sync_db_changed.emit()

    def dump_sync_db(self):
        if not self.sync_db:
            return
        print('Dumping sync database')
        text = self.path_edit.text()
        path = os.path.join(text, 'sync.pkl')
        with open(path, 'wb') as f:
            pickle.dump(self.sync_db, f)
        self.sync_db_saved.emit()

    def meta_artist_seperator_editing_finished(self):
        if self.sync_db:
            text = self.meta_artist_seperator_edit.text()
            self.sync_db['templates']['artist']['seperator']['filename'] = text
            self.sync_db_changed.emit()

    def file_artist_seperator_editing_finished(self):
        if self.sync_db:
            text = self.file_artist_seperator_edit.text()
            self.sync_db['templates']['artist']['seperator']['metadata'] = text
            self.sync_db_changed.emit()

    def create_sync_db(self):
        path = self.path_edit.text()
        db = get_sync_db_template()
        print('Creating sync database')
        with open(os.path.join(path, 'sync.pkl'), 'wb') as f:
            pickle.dump(db, f)
        if self.path_editing_finished():
            self.make_syncable_button.setDisabled(True)

    def sync_lib(self):
        #region Disable everything
        self.uuid_label.setDisabled(False)
        self.name_label.setDisabled(False)
        self.artist_label.setDisabled(False)
        self.sync_start_button.setDisabled(True)
        self.path_edit.setDisabled(True)
        self.path_button.setDisabled(True)
        self.confirm_path_button.setDisabled(True)
        self.write_meta_checkbox.setDisabled(True)
        self.path_template_checkbox.setDisabled(True)
        self.path_template_edit.setDisabled(True)
        self.blacklist_widget.setDisabled(True)
        self.meta_artist_seperator_label.setDisabled(True)
        self.meta_artist_seperator_edit.setDisabled(True)
        self.file_artist_seperator_label.setDisabled(True)
        self.file_artist_seperator_edit.setDisabled(True)
        #endregion

        worker = Worker(self.sync_lib_worker_task)
        worker.signals.finished.connect(self.sync_finished)
        self.sync_start_button.setDisabled(True)
        self.threadpool.start(worker)

    def sync_finished(self):
        self.status_label.setText('Status: Finished.')
        self.uuid_label.setDisabled(True)
        self.name_label.setDisabled(True)
        self.artist_label.setDisabled(True)

    def sync_lib_worker_task(self):
        self.status_label.setText('Status: Repairing synced library')
        repair_sync_db(self.path_edit.text())
        self.status_label.setText('Status: Syncing library')
        sync(self.path_edit.text(), self.update_sync_info)

    def update_sync_info(self, uuid: UUID):
        self.uuid_label.setText('UUID: ' + str(uuid))
        self.name_label.setText('Name: ' + database['music'][uuid]['title'] if database['music'][uuid]['title'] else 'No Name')
        self.artist_label.setText('Artists: ' + str([database['artists'][artist]['name'] for artist in database['music'][uuid]['artists']]))
        return True
